#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <limits.h>

#include "dbgutils.h"

static module *modules=0;
static int nummod=0;
bool modules_inorder=false;
int maxmodnamelen=0; /* Length of longest module name */

static module m_key; /* Module search key */
static gpa_func f_key; /* Function search key */

/*

				Utility functions

*/

void *safe_realloc(void *ptr,size_t size)
{
	ptr = realloc(ptr,size);
	if(!ptr)
	{
		fprintf(stderr,"realloc failed - out of memory\n");
		exit(1);
	}
	return ptr;
}

void *safe_malloc(size_t size)
{
	void *ptr = malloc(size);
	if(!ptr)
	{
		fprintf(stderr,"malloc failed - out of memory\n");
		exit(1);
	}
	return ptr;
}

FILE *safe_fopen(const char *name,const char *mode)
{
	FILE *f;
	if(!name)
	{
		fprintf(stderr,"safe_fopen: No filename given\n");
		exit(1);
	}
	f = fopen(name,mode);
	if(!f)
	{
		fprintf(stderr,"fopen() failed on file \"%s\", mode \"%s\"\n",name,mode);
		exit(1);
	}
	return f;
}

void trim(char *buf)
{
	char *end = buf+strlen(buf);
	while((end > buf) && (end[-1] <= 32))
		*--end = 0;
}

/*

				Search internals

*/

static int ModuleCompareFunc(const void *a,const void *b)
{
	module *am = (module *) a;
	module *bm = (module *) b;
	if(am->start < bm->start)
		return -1;
	else if(am->start > bm->start)
		return 1;
	fprintf(stderr,"Warning: Modules %s and %s have identical base addresses\n",am->name,bm->name);
	return 0;
}

static int FunctionCompareFunc(const void *a,const void *b)
{
	gpa_func *af = (gpa_func *) a;
	gpa_func *bf = (gpa_func *) b;
	if(af->start < bf->start)
		return -1;
	else if(af->start > bf->start)
		return 1;
	fprintf(stderr,"Warning: Functions %s and %s have identical base addresses\n",af->name,bf->name);
	return 0;
}

static int FindAddressFunc(const void *a,const void *b)
{
	module *m;
	int flip;
	if(a == &m_key)
	{
		m = (module *)b;
		flip = 1;
	}
	else
	{
		m = (module *)a;
		flip = -1;
	}
	if(m_key.start < m->start)
		return -flip;
	if(m->end == 0)
	{
		/* Module with unknown size; check start address of next module */
		if(m_key.start < m[1].start)
			return 0;
	}
	else if(m_key.start < m->end)
		return 0;
	return flip;
}

static int FindFuncFunc(const void *a,const void *b)
{
	gpa_func *f;
	int flip;
	if(a == &f_key)
	{
		f = (gpa_func *)b;
		flip = 1;
	}
	else
	{
		f = (gpa_func *)a;
		flip = -1;
	}
	if(f_key.start < f->start)
		return -flip;
	if(f_key.start < f->end)
		return 0;
	return flip;
}

static int FindLineFunc(const void *a,const void *b)
{
	gpa_line *al = (gpa_line *) a;
	gpa_line *bl = (gpa_line *) b;
	if(al->addr < bl->addr)
		return -1;
	else if(al->addr > bl->addr)
		return 1;
	return 0;
}

/*

				Lookup functions

*/

module *FindModule(const char *name)
{
	int i;
	for(i=0;i<nummod;i++)
		if(!strcmp(modules[i].name,name))
			return &modules[i];
	return 0;
}

module *FindAddress(uint32_t addr)
{
	if(!nummod)
		return 0;
	if(!modules_inorder)
	{
		qsort(modules,nummod,sizeof(module),ModuleCompareFunc);
		modules_inorder = true;
		/* If end address of last module unknown, set to max */
		if(!modules[nummod-1].end)
			modules[nummod-1].end = 0xffffffff;
	}
	m_key.start = m_key.end = addr;
	return (module *) bsearch(&m_key,modules,nummod,sizeof(module),FindAddressFunc);
}

gpa_func *FindFunc(module *m,uint32_t addr)
{
	f_key.start = f_key.end = addr;
	return (gpa_func *) bsearch(&f_key,m->funcs,m->numfunc,sizeof(gpa_func),FindFuncFunc);
}

gpa_line *FindLine(module *m,uint32_t addr)
{
	gpa_line key;
	key.addr = addr;
	return (gpa_line *) bsearch(&key,m->lines,m->numline,sizeof(gpa_line),FindLineFunc);
}

/*

				Data loading/management

*/

module *AddModule(uint32_t start,uint32_t end,const char *name)
{
	module *m;
	/* If last module end address was fixed up, unfix it, in case our new module will end up higher than it */
	if((modules_inorder) && (nummod) && (modules[nummod-1].end == 0xffffffff))
		modules[nummod-1].end = 0;
	modules = safe_realloc(modules,sizeof(module)*(++nummod));
	m = &modules[nummod-1];
	memset(m,0,sizeof(module));
	m->start = start;
	m->end = end;
	m->name = safe_malloc(strlen(name)+1);
	strcpy(m->name,name);
	m->detail = 3; /* Default to low detail */
	modules_inorder=false;
	maxmodnamelen = MAX(strlen(name),maxmodnamelen);
	return m;
}

void AddGPAFunction(module *m,const char *name,uint32_t start,uint32_t end)
{
	gpa_func *f;
	if(m->numfunc == m->funcbufsize)
	{
		m->funcbufsize += 128;
		m->funcs = safe_realloc(m->funcs,sizeof(gpa_func)*m->funcbufsize);
	}
	f = &m->funcs[m->numfunc++];
	memset(f,0,sizeof(gpa_func));
	f->start = start;
	f->end = end;
	f->name = safe_malloc(strlen(name)+1);
	strcpy(f->name,name);
}

void AddGPALine(module *m,uint32_t addr,uint32_t line,const char *file)
{
	gpa_line *l;
	if(m->numline == m->linebufsize)
	{
		m->linebufsize += 512;
		m->lines = safe_realloc(m->lines,sizeof(gpa_line)*m->linebufsize);
	}
	l = &m->lines[m->numline++];
	memset(l,0,sizeof(gpa_line));
	l->addr = addr;
	l->line = line;
	l->file = (char *) file; /* Shared string; no need to copy */
}

void KillGPA(module *m)
{
	while(m->numfunc--)
		free(m->funcs[m->numfunc].name);
	if(m->funcs)
		free(m->funcs);
	if(m->numline)
	{
		/* May break if line number ordering changes from when we read the file */
		char *sourcefile=0;
		while(m->numline--)
		{
			if(m->lines[m->numline].file != sourcefile)
			{
				sourcefile = m->lines[m->numline].file;
				free(sourcefile);
			}
		}
		free(m->lines);
	}
	m->numfunc = m->funcbufsize = 0;
	m->funcs = 0;
	m->numline = m->linebufsize = 0;
	m->lines = 0;
}

void KillModule(module *m)
{
	KillGPA(m);
	free(m->name);
	int i = m-modules;
	if(i != --nummod)
		memmove(m,m+1,(nummod-i)*sizeof(module));
}

void LoadGPA(module *m,const char *gpa)
{
	char buf[1024];
	char *sourcefile=0;
	FILE *f = safe_fopen(gpa,"r");
	uint32_t offset=0; // Offset to apply to all addresses to map GPA address -> runtime address
	int offset_valid=0;
	int section=-1; // 0=start address, 1=functions, 2=lines
	KillGPA(m);
	buf[0] = 0;
	while(!feof(f) && fgets(buf,1024,f))
	{
		trim(buf);
		if(buf[0] == '[')
		{
			int newsection=-1;
			if(!strcmp(buf,"[SECTIONS]"))
				newsection = 0;
			else if(!strcmp(buf,"[FUNCTIONS]"))
				newsection = 1;
			else if(!strcmp(buf,"[SOURCE LINES]"))
				newsection = 2;
			/* To start with, offset is just the lowest section listed in the file. We then translate it to a true offset here. */
			if((section == 0) && (newsection != section))
				offset = m->start-offset;
			section = newsection;
		}
		else if(buf[0])
		{
			switch(section)
			{
			case 0:
				{
					uint32_t start;
					uint32_t end;
					char c;
					if(sscanf(buf,"R%c %08x..%08x",&c,&start,&end) == 3)
					{
						if(!offset_valid || (start < offset))
						{
							offset = start;
							offset_valid = 1;
						}
					}
				}
				break;
			case 1:
				{
					uint32_t start;
					uint32_t end;
					char *name = buf+strlen(buf)+1;
					if(sscanf(buf,"%s %08x..%08x",name,&start,&end) == 3)
						AddGPAFunction(m,name,start+offset,end+offset);
				}
				break;
			case 2:
				if(!strncmp(buf,"File: ",6))
				{
					sourcefile = safe_malloc(strlen(buf)-5);
					strcpy(sourcefile,buf+6);
				}
				else if(sourcefile)
				{
					uint32_t line;
					uint32_t addr;
					if(sscanf(buf,"%d %08x",&line,&addr) == 2)
						AddGPALine(m,addr+offset,line,sourcefile);
				}
				break;
			}
		}
		buf[0] = 0;
	}
	fclose(f);
	/* For the moment, assume that the entries in the GPA file are already sorted in address order */
	printf("GPA for module '%s' loaded. %d functions, %d line numbers.\n",m->name,m->numfunc,m->numline);
	if(m->detail == 3)
		m->detail = 2; /* Increase detail level now GPA is loaded */
}

void LoadAbsolute(module *m,const char *absol)
{
	KillGPA(m);
	FILE *f = safe_fopen(absol,"r");
	fseek(f,0,SEEK_END);
	int sz = ftell(f);
	fseek(f,0,SEEK_SET);
	char *buf = (char *) safe_malloc(sz+4);
	fread(buf,sz,1,f);
	fclose(f);
	int pos=0;
	char *lastfn=0;
	uint32_t lastpos=0;
	for(pos=0;pos<sz;pos+=4)
	{
		unsigned int word = *((unsigned int *)(buf+pos));
		if((word & ~0xff) == 0xff000000)
		{
			word &= 0xff;
			if((word <= pos) && (word))
			{
				/* Check it's a valid function name */
				char *fn = buf+pos-word;
				int c = word;
				if(isalpha(*fn) || (*fn=='_') || (*fn=='^'))
				{
					do{
						fn++;
						c--;
					} while(c && (isalnum(*fn) || (*fn=='_')));
					if((*fn == 0) && c)
					{
						do {
							fn++;
							c--;
						} while(c && !*fn);
						if(!c)
						{
							/* Looks valid! */
							if(lastfn)
								AddGPAFunction(m,lastfn,lastpos,m->start+pos-word-1);
							lastfn = buf+pos-word;
							lastpos = m->start+pos+4;
						}
					}
				}
			}
		}
	}
	if(lastfn)
		AddGPAFunction(m,lastfn,lastpos,m->start+sz);
	free(buf);
	printf("Absolute file '%s' loaded. %d functions.\n",m->name,m->numfunc);
	if(m->detail == 3)
		m->detail = 2; /* Increase detail level now GPA is loaded */
}

void LoadSyms(module *m,const char *syms)
{
	if(!m->start)
	{
		printf("LoadSyms: module end address must be known\n");
		return;
	}
	char buf[1024];
	KillGPA(m);
	FILE *f = safe_fopen(syms,"r");
	buf[0] = 0;
	while(!feof(f) && fgets(buf,1024,f))
	{
		trim(buf);
		if(buf[0] && strcmp(buf,"Symbol Table"))
		{
			/* Looks like a symbol definition */
			char *c = strchr(buf,' ');
			if(c)
			{
				*c++ = 0;
				uint32_t address = strtoul(c,NULL,16);
				if(address && (address >= m->start) && (address < m->end))
				{
					/* Ignore ...$$Base and ...$$Limit symbols, and $<char> symbols */
					if(!strstr(buf,"$$Base") && !strstr(buf,"$$Limit") && ((buf[0] != '$') || (strlen(buf) > 2)))
						AddGPAFunction(m,buf,address,0);
				}
			}
		}
		buf[0] = 0;
	}
	fclose(f);
	/* Sort functions and calculate end addresses */
	qsort(m->funcs,m->numfunc,sizeof(gpa_func),FunctionCompareFunc);
	for(int i=0;i<m->numfunc;i++)
	{
		if(!m->funcs[i].end)
			m->funcs[i].end = (i<m->numfunc-1)?(m->funcs[i+1].start-1):m->end;
	}
	printf("Symbols for module '%s' loaded. %d functions.\n",m->name,m->numfunc);
	if(m->detail == 3)
		m->detail = 2; /* Increase detail level now GPA is loaded */
}

void LoadROM(const char *builddir,const char *romname)
{
	char namebuf[1024];
	char buf[1024];
	char env[1024];
	KillROMModules();
	sprintf(namebuf,"%s.BuildSys.Logs.%s",builddir,romname);
	FILE *f = safe_fopen(namebuf,"r");
	buf[0] = 0;
	int line = 0,romline = INT_MAX;
	while(!feof(f) && fgets(buf,1024,f))
	{
		trim(buf);
		if(line++ == 1)
		{
			if(strncmp(buf,"Started ",8))
			{
				fprintf(stderr,"Bad build log file\n");
				exit(1);
			}
			char *c = buf+8;
			char *c2 = env;
			while(*c > 32)
			{
				*c2++ = *c++;
			}
			if(*c != 32)
			{
				fprintf(stderr,"Bad build log file\n");
				exit(1);
			}
			*c2 = 0;
		}
		else if(!strcmp(buf,"Summary of linked ROM contents..."))
		{
			romline = line+4;
		}
		else if(romline <= line)
		{
			char *c = strchr(buf,' ');
			if(!c)
			{
				/* End of module list */
				break;
			}
			*c++ = 0;
			uint32_t base = strtoul(c,&c,16);
			if(!base)
			{
				fprintf(stderr,"Bad build log file\n");
				exit(1);
			}
			uint32_t length = strtoul(c,NULL,16);
			if(!length)
			{
				fprintf(stderr,"Bad build log file\n");
				exit(1);
			}
//			printf("Module: %s Base: %08x Length: %08x\n",buf,base,length);
			AddModule(base,base+length-1,buf);
		}
		buf[0] = 0;
	}
	if(!nummod)
	{
		fprintf(stderr,"No modules found\n");
		exit(1);
	}
	/* Now load GPA/symbols for each module, as appropriate */
	sprintf(namebuf,"%s.BuildSys.ModuleDB",builddir);
	f = safe_fopen(namebuf,"r");
	buf[0] = 0;
	while(!feof(f) && fgets(buf,1024,f))
	{
		trim(buf);
		if(buf[0] && (buf[0] != '#'))
		{
			char *c = strchr(buf,' ');
			char *type;
			char *source;
			char *instdir;
			char *modname;
			if(c)
			{
				*c++ = 0;
				while(*c == ' ')
					c++;
				type = c;
				c = strchr(c,' ');
			}
			if(c)
			{
				*c++ = 0;
				while(*c == ' ')
					c++;
				source = c;
				c = strchr(c,' ');
			}
			if(c)
			{
				*c++ = 0;
				while(*c == ' ')
					c++;
				instdir = c;
				c = strchr(c,' ');
			}
			if(c)
			{
				*c++ = 0;
				while(*c == ' ')
					c++;
				modname = c;
				c = strchr(c,' ');
			}
			if(c)
				*c = 0;
			if(*modname)
			{
				module *m = FindModule(buf);
				if(m)
				{
					if(!strcmp(type,"ASM"))
					{
						/* Look for GPA file */
						sprintf(namebuf,"%s.Install.%s.%s.%s_gpa",builddir,env,instdir,modname);
//						printf("%s\n",namebuf);
						FILE *f = fopen(namebuf,"r");
						if(f)
						{
							fclose(f);
							LoadGPA(m,namebuf);
						}
					}
					else if(!strcmp(type,"C"))
					{
						/* Look for symbols file */
						sprintf(namebuf,"%s.Install.%s.%s.%s_sym",builddir,env,instdir,modname);
//						printf("%s\n",namebuf);
						FILE *f = fopen(namebuf,"r");
						if(f)
						{
							fclose(f);
							LoadSyms(m,namebuf);
						}
					}
				}
			}
		}
		buf[0] = 0;
	}
	fclose(f);
	for(int i=0;i<nummod;i++)
	{
		if(!modules[i].numfunc)
		{
			printf("Failed to find debug info for module '%s'\n",modules[i].name);
		}
	}
	printf("%d modules loaded\n",nummod);
}

void KillROMModules(void)
{
	FindAddress(0); /* Ensure modules are in order */
	int i=nummod;
	while(i && (modules[i-1].start >= 0xfc000000))
	{
		i--;
		if (modules[i].start < 0xffff0000)
		{
			KillModule(&modules[i]);
		}
	}
}

void KillModules(void)
{
	while(nummod)
		KillModule(&modules[nummod-1]);
	if(modules)
		free(modules);
	modules = 0;
	nummod = 0;
	modules_inorder = false;
	maxmodnamelen = 0;
}


